using System;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;

namespace NetCorePal.Extensions.Domain.SourceGenerators;

[Generator]
public class EntityIdCodeGenerators : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        var syntaxProvider = context.SyntaxProvider
            .CreateSyntaxProvider(
                predicate: (node, _) => node is TypeDeclarationSyntax,
                transform: (syntaxContext, _) => (TypeDeclarationSyntax)syntaxContext.Node)
            .Where(tds => tds != null);

        var compilationAndTypes = context.CompilationProvider.Combine(syntaxProvider.Collect());

        context.RegisterSourceOutput(compilationAndTypes, (spc, source) =>
        {
            var (compilation, typeDeclarations) = source;
            foreach (var tds in typeDeclarations)
            {
                var semanticModel = compilation.GetSemanticModel(tds.SyntaxTree);
                Generate(spc, semanticModel, tds, SourceType.Int64);
                Generate(spc, semanticModel, tds, SourceType.Int32);
                Generate(spc, semanticModel, tds, SourceType.String);
                Generate(spc, semanticModel, tds, SourceType.Guid);
            }
        });
    }

    private void Generate(SourceProductionContext context, SemanticModel semanticModel,
        TypeDeclarationSyntax classDef, SourceType sourceType)
    {
        var symbol = semanticModel.GetDeclaredSymbol(classDef);
        if (symbol is not INamedTypeSymbol namedTypeSymbol) return;

        var isEntityId = namedTypeSymbol.Interfaces
            .SingleOrDefault(t => t.Name.StartsWith($"I{sourceType}StronglyTypedId"));
        if (isEntityId == null) return;

        string ns = namedTypeSymbol.ContainingNamespace.ToString();
        string className = namedTypeSymbol.Name;

        string tryParseMethod = GenerateTryParseMethod(className, sourceType);

        string operatorString = sourceType == SourceType.String
            ? ""
            : $@"
        public static bool operator <({className} left, {className} right)
        {{
            return left.Id < right.Id;
        }}

        public static bool operator >({className} left, {className} right)
        {{
            return left.Id > right.Id;
        }}
        
        public static bool operator <=({className} left, {className} right)
        {{
            return left.Id <= right.Id;
        }}

        public static bool operator >=({className} left, {className} right)
        {{
            return left.Id >= right.Id;
        }}                                
";

        string source = $@"#nullable enable
// <auto-generated/>
using NetCorePal.Extensions.Domain;
using System;
using System.ComponentModel;
namespace {ns}
{{
    /// <summary>
    /// Strongly typed id for {className}
    /// </summary>
    /// <param name=""Id"">The Inner Id</param>
    [TypeConverter(typeof(EntityIdTypeConverter<{className}, {sourceType}>))]
    public partial record {className}({sourceType} Id)
    {{
        ///// <summary>
        ///// implicit operator
        ///// </summary>
        //public static implicit operator {sourceType}({className} id) => id.Id;
        ///// <summary>
        ///// implicit operator
        ///// </summary>
        //public static implicit operator {className}({sourceType} id) => new {className}(id);

        /// <summary>
        /// Id.ToString()
        /// </summary>
        public override string ToString()
        {{
            return Id.ToString();
        }}

        /// <summary>
        /// TryParse method for converting string to {className}
        /// </summary>
        /// <param name=""input"">The input string to parse</param>
        /// <param name=""output"">The parsed {className} if successful</param>
        /// <returns>True if parsing was successful, false otherwise</returns>
        {tryParseMethod}

        /// <summary>
        /// Equals
        /// </summary>
        public virtual bool Equals({className}? other)
        {{
            if (ReferenceEquals(null, other)) return false;
            if (ReferenceEquals(this, other)) return true;
            return Id == other.Id;
        }}
        /// <summary>
        /// Id.GetHashCode()
        /// </summary>
        public override int GetHashCode() => Id.GetHashCode();
{operatorString}
    }}
}}
";
        context.AddSource($"{ns}.{className}.g.cs", SourceText.From(source, Encoding.UTF8));
    }

    private string GenerateTryParseMethod(string className, SourceType sourceType)
    {
        return sourceType switch
        {
            SourceType.Int64 => $@"public static bool TryParse(string? input, out {className} output)
        {{
            output = default!;

            if (string.IsNullOrEmpty(input))
            {{
                return false;
            }}

            if (long.TryParse(input, out var longValue))
            {{
                output = new {className}(longValue);
                return true;
            }}

            return false;
        }}",

            SourceType.Int32 => $@"public static bool TryParse(string? input, out {className} output)
        {{
            output = default!;

            if (string.IsNullOrEmpty(input))
            {{
                return false;
            }}

            if (int.TryParse(input, out var intValue))
            {{
                output = new {className}(intValue);
                return true;
            }}

            return false;
        }}",

            SourceType.String => $@"public static bool TryParse(string? input, out {className} output)
        {{
            output = default!;

            if (string.IsNullOrEmpty(input))
            {{
                return false;
            }}

            output = new {className}(input);
            return true;
        }}",

            SourceType.Guid => $@"public static bool TryParse(string? input, out {className} output)
        {{
            output = default!;

            if (string.IsNullOrEmpty(input))
            {{
                return false;
            }}

            if (Guid.TryParse(input, out var guidValue))
            {{
                output = new {className}(guidValue);
                return true;
            }}

            return false;
        }}",

            _ => throw new ArgumentException($"Unsupported source type: {sourceType}")
        };
    }

    enum SourceType
    {
        String,
        Int64,
        Int32,
        Guid
    }
}